Imports System
Imports System.Net
Imports System.Xml
Imports System.Collections.Generic
Imports System.Security.Principal
Imports System.ServiceModel
Imports System.ServiceModel.Security
Imports System.Text.RegularExpressions
Imports System.Xml.Linq
Imports SPRemoteService.Impl.SP.Copy
Imports SPRemoteService.Impl.SP.Lists
Imports SPRemoteService.Impl.SP.Webs
Imports SPRemoteService.Impl.SP.SiteData
Imports System.Threading

Partial Public Class SharePointConnector

    Public Shared ReadOnly SharePointNameSpace As XNamespace = "http://schemas.microsoft.com/sharepoint/soap/"

    Private _username As String = String.Empty
    Private _password As String = String.Empty
    Private _domain As String = String.Empty
    Private _spURL As Uri
    Private _authenticated As Boolean = False
    Private _isConnected As Boolean = False

    Private _networkCredentials As NetworkCredential

    Private _spHelpers As SharePointHelpers

    Public Sub New()
    End Sub

    Public Class RetryLogic

        Public Enum CallerType
            WebService = 1
            CSOM = 2
            Both = 3
        End Enum

        Private _callerType As CallerType
        Private _retryCount As Integer
        Private _maxRetries As Integer

        Public Sub New(ByVal callerType As CallerType, ByVal maxRetries As Integer)
            _callerType = callerType
            _maxRetries = maxRetries
        End Sub

        Public Function ShouldReThrow(ByVal ex As Exception) As Boolean
            If (IsNetworkException(ex) Or TypeOf (ex) Is ConditionNotMetException) Then
                _retryCount += 1
                If (_retryCount <= _maxRetries) Then Return False
            Else
                If Not (ex.InnerException Is Nothing) Then
                    Return ShouldReThrow(ex.InnerException)
                End If
            End If

            Return True
        End Function

        Private Function IsNetworkException(ByVal ex As Exception) As Boolean
            If (_callerType And CallerType.WebService) = CallerType.WebService Then
                If TypeOf (ex) Is System.ServiceModel.EndpointNotFoundException Or _
                    TypeOf (ex) Is System.TimeoutException Or _
                    TypeOf (ex) Is System.ServiceModel.CommunicationException Then
                    Return True
                End If
            End If
            If (_callerType And CallerType.CSOM) = CallerType.CSOM Then
                If TypeOf (ex) Is System.Net.WebException Then
                    Return True
                End If
            End If
            Return False
        End Function

    End Class

    Public Sub Retry(ByVal op As Action, ByVal callerType As RetryLogic.CallerType)
        Dim retryLogic = New RetryLogic(callerType, 5)
        Do While True
            Try
                op()
                Exit Do
            Catch ex As Exception
                If retryLogic.ShouldReThrow(ex) Then Throw
            End Try
        Loop
    End Sub

    Public Function Retry(Of T)(ByVal op As Func(Of T), ByVal callerType As RetryLogic.CallerType) As T
        Dim retryLogic = New RetryLogic(callerType, 5)
        Do While True
            Try
                Return op()
            Catch ex As Exception
                If retryLogic.ShouldReThrow(ex) Then Throw
            End Try
        Loop
    End Function

    Public Function Retry(Of T)(ByVal op As Func(Of T), ByVal condition As Func(Of T, Boolean), ByVal callerType As RetryLogic.CallerType) As T
        Dim retryLogic = New RetryLogic(callerType, 5)
        Do While True
            Try
                Dim res = op()
                If Not condition(res) Then
                    Throw New Exception()
                End If

                Return res
            Catch ex As Exception
                If retryLogic.ShouldReThrow(ex) Then Throw
                Thread.Sleep(1000)
            End Try
        Loop
    End Function

    Public Class ConditionNotMetException
        Inherits Exception
    End Class

    Public Sub Connect(ByVal username As String, _
                        ByVal password As String, _
                        ByVal siteUrl As Uri, _
                        ByVal domain As String, _
                        ByVal windowsCredentials As Boolean)

        _authenticated = False
        _isConnected = False

        If Not windowsCredentials Then
            Try
                _username = username
            Catch ex As Exception
                Throw
            End Try
            _password = password
            _domain = domain
        Else
            _username = WindowsIdentity.GetCurrent().Name
        End If

        _spURL = New Uri(Regex.Replace(siteUrl.ToString(), "[ /]$", String.Empty) + "/")

        _spHelpers = New SharePointHelpers(Me)

        Dim sCurrentUserName As String = ""
        Try
            If Not windowsCredentials Then
                sCurrentUserName = _username

                _networkCredentials = New NetworkCredential(_username, _
                                                            _password, _
                                                            _domain)
            Else
                sCurrentUserName = WindowsIdentity.GetCurrent().Name.ToString()

                _networkCredentials = CredentialCache.DefaultNetworkCredentials
            End If

            _authenticated = True

        Catch e As Exception
            Throw
        End Try

        Try
            Dim siteNode As XmlNode

            siteNode = Retry(Function() GetWebsService(_spURL).GetWeb(".").ToXmlElement, RetryLogic.CallerType.WebService)

            If siteNode Is Nothing Then
                Throw New Exception("Cannot connect")
            End If
        Catch e As Exception
            Throw
        End Try


        _isConnected = True
    End Sub

    Public ReadOnly Property UserName() As String
        Get
            Return _username
        End Get
    End Property

    Public ReadOnly Property Password() As String
        Get
            Return _password
        End Get
    End Property

    Public ReadOnly Property Domain() As String
        Get
            Return _domain
        End Get
    End Property

    Public ReadOnly Property SiteUrl() As Uri
        Get
            Return _spURL
        End Get
    End Property

    Public ReadOnly Property IsConnected() As Boolean
        Get
            Return _isConnected
        End Get
    End Property

    Friend ReadOnly Property Authenticated() As Boolean
        Get
            Return _authenticated
        End Get
    End Property

    Friend ReadOnly Property Credentials() As NetworkCredential
        Get
            Return _networkCredentials
        End Get
    End Property

    Function CreateBinding(ByVal url As Uri, ByVal name As String) As BasicHttpBinding
        Dim binding As New BasicHttpBinding With {.Name = name}
        binding.Security.Mode = url.WcfSecurityMode
        binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Ntlm

        binding.MaxBufferPoolSize = Int32.MaxValue
        binding.MaxReceivedMessageSize = Int32.MaxValue
        binding.ReaderQuotas.MaxArrayLength = Int32.MaxValue
        binding.ReaderQuotas.MaxBytesPerRead = Int32.MaxValue
        Return binding
    End Function

    Sub SetCredentials(Of T As Class)(ByVal service As ClientBase(Of T))
        service.ClientCredentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation
        service.ClientCredentials.Windows.ClientCredential = _networkCredentials

    End Sub

    Public Function GetCopyService(ByVal url As Uri) As CopySoapClient
        Dim binding = CreateBinding(url, "CopySoap")
        Dim endpointAddress As New EndpointAddress(url.Append("_vti_bin/Copy.asmx"))
        Dim service As New CopySoapClient(binding, endpointAddress)
        SetCredentials(service)
        Return service
    End Function

    Public Function GetListsService(ByVal url As Uri) As ListsSoapClient
        Dim binding = CreateBinding(url, "ListsSoap")
        Dim endpointAddress As New EndpointAddress(url.Append("_vti_bin/Lists.asmx"))
        Dim service As New ListsSoapClient(binding, endpointAddress)
        SetCredentials(service)
        Return service
    End Function

    Public Function GetSiteDataService(ByVal url As Uri) As SiteDataSoapClient
        Dim binding = CreateBinding(url, "SiteDataSoap")
        Dim endpointAddress As New EndpointAddress(url.Append("_vti_bin/SiteData.asmx"))
        Dim service As New SiteDataSoapClient(binding, endpointAddress)
        SetCredentials(service)
        Return service

    End Function

    Public Function GetWebsService(ByVal url As Uri) As WebsSoapClient
        Dim binding = CreateBinding(url, "WebsSoap")
        Dim endpointAddress As New EndpointAddress(url.Append("_vti_bin/Webs.asmx"))
        Dim service As New WebsSoapClient(binding, endpointAddress)
        SetCredentials(service)
        Return service
    End Function

    Public Function GetSite(ByVal url As Uri) As SharePointContainer
        Return _spHelpers.GetSpSite(url)
    End Function

    Public Function GetSiteList(ByVal url As Uri) As List(Of SharePointContainer)
        Return _spHelpers.GetSiteList(url)
    End Function

    Public Function GetLibraryList(ByVal url As Uri) As List(Of SharePointLibrary)
        Return _spHelpers.GetLibraryList(url)
    End Function

    Public Function GetLibrary(ByVal url As Uri) As SharePointLibrary
        Return Retry(Function() _spHelpers.GetLibrary(url), RetryLogic.CallerType.WebService)
    End Function

    Public Function GetFolder(ByVal siteUrl As Uri, ByVal libraryName As String, ByVal folder As String) As SharePointContainer
        Return _spHelpers.GetFolder(siteUrl, libraryName, folder)
    End Function

    Function GetFolderList(ByVal url As Uri, ByVal listName As String, _
                           ByVal libPath As String, ByVal rowLimit As String) As List(Of SharePointContainer)
        Return _spHelpers.GetFolderList(url, listName, libPath, rowLimit)
    End Function

    Function GetDocumentList(ByVal url As Uri, ByVal listName As String, _
                           ByVal libPath As String, ByVal rowLimit As String) As List(Of SharePointContainer)
        Return _spHelpers.GetDocumentList(url, listName, libPath, rowLimit)
    End Function

    Function GetDocument(ByVal url As Uri, ByVal fileUrl As String) As Byte()
        Dim getItemFieldInfos() As FieldInformation = {}
        Dim byteArr = New Byte() {}
        Dim nGetItemResult = GetCopyService(url).GetItem(fileUrl, getItemFieldInfos, byteArr)
        Return byteArr
    End Function

    Function GetObjectsOfList(ByVal url As Uri, ByVal list As String, ByVal matchField As SharePointField, ByVal matchValues As String(),
                          ByVal selectFields As List(Of SharePointField), ByVal rowLimit As Integer,
                          Optional ByVal folderName As String = "", Optional ByVal cacheResults As Boolean = True) As DataSet
        Dim dataSet As DataSet = Nothing

        Dim options = <QueryOptions>
                          <DateInUtc>TRUE</DateInUtc>
                          <Folder><%= folderName %></Folder>
                      </QueryOptions>

        Dim query As XElement = GenerateQuery(matchValues, matchField)

        Dim fields = <ViewFields>
                         <FieldRef Name=<%= matchField.Name %>></FieldRef>
                         <%= From f In selectFields
                             Select <FieldRef Name=<%= f.Name %>></FieldRef> %>
                     </ViewFields>

        Dim listItems As XElement
        Try
            listItems = Retry(Function() GetListsService(url).GetListItems(list, _
                                                                         Nothing, _
                                                                         query, _
                                                                         fields, _
                                                                         rowLimit.ToString(), _
                                                                         options, _
                                                                         String.Empty), _
                                                                     RetryLogic.CallerType.WebService)
        Catch e As Exception
            Throw
        End Try

        Dim results = listItems.Elements()(0).Elements().ToList()

        dataSet = New DataSet()

        For Each v In matchValues
            Dim table As DataTable = New DataTable(v)
            table.Columns.AddRange((From f In selectFields
                                   Select New DataColumn(f.Name)).ToArray())

            Dim tempVal = v
            Dim matchingElements = results.Where(Function(f) FilterResults(f.Attribute("ows_" & matchField.Name).Value, tempVal, matchField.Type))

            For Each element In matchingElements
                Dim newRow = table.NewRow()
                For Each field In selectFields
                    Dim attr = element.Attribute("ows_" & field.Name)
                    If attr IsNot Nothing Then
                        newRow(field.Name) = GetFormattedResult(element.Attribute("ows_" & field.Name).Value, field.Type)
                    End If
                Next
                table.Rows.Add(newRow)
            Next

            dataSet.Tables.Add(table)
        Next

        Return dataSet
    End Function

    Function GetObjectsOfList(ByVal url As Uri, ByVal list As String,
                          ByVal selectFields As List(Of SharePointField), ByVal rowLimit As Integer,
                          Optional ByVal folderName As String = "", Optional ByVal cacheResults As Boolean = True) As DataSet
        Dim dataSet As DataSet = Nothing

        Dim options = <QueryOptions>
                          <DateInUtc>TRUE</DateInUtc>
                          <Folder><%= folderName %></Folder>
                      </QueryOptions>

        Dim query = <Query>
                        <Where>
                        </Where>
                    </Query>

        Dim fields = <ViewFields>
                         <%= From f In selectFields
                             Select <FieldRef Name=<%= f.Name %>></FieldRef> %>
                     </ViewFields>

        Dim listItems As XElement
        Try
            listItems = Retry(Function() GetListsService(url).GetListItems(list, _
                                                                         Nothing, _
                                                                         query, _
                                                                         fields, _
                                                                         rowLimit.ToString(), _
                                                                         options, _
                                                                         String.Empty), _
                                                                     RetryLogic.CallerType.WebService)
        
        Catch e As Exception
            Throw
        End Try

        Dim results = listItems.Elements()(0).Elements().ToList()

        ' Create and return DataSet from results
        dataSet = New DataSet()

        Dim table As DataTable = New DataTable()
        table.Columns.AddRange((From f In selectFields
                               Select New DataColumn(f.Name)).ToArray())

        ' Find all elements matching the lookup value (used in the query)
        Dim matchingElements = results

        For Each element In matchingElements
            Dim newRow = table.NewRow()
            For Each field In selectFields
                Dim attr = element.Attribute("ows_" & field.Name)
                If attr IsNot Nothing Then
                    newRow(field.Name) = GetFormattedResult(element.Attribute("ows_" & field.Name).Value, field.Type)
                End If
            Next
            table.Rows.Add(newRow)
        Next

        dataSet.Tables.Add(table)

        Return dataSet
    End Function

    Function GetObjectIDFromPath(ByVal url As Uri, ByVal itemUrl As String) As String
        Dim webId As String = String.Empty
        Dim bucketId As String = String.Empty
        Dim listId As String = String.Empty
        Dim itemId As String = String.Empty
        Try
            Retry(Function() GetSiteDataService(url).GetURLSegments(itemUrl, webId, bucketId, listId, itemId), RetryLogic.CallerType.WebService)

        Catch e As Exception
            Throw
        End Try
        Return itemId
    End Function

    Private Function GetFormattedResult(ByVal serverValue As String, ByVal type As SharePointField.TypeEnum) As String
        Return serverValue
    End Function

    Private Function FilterResults(ByVal serverValue As String, ByVal matchValue As String, ByVal type As SharePointField.TypeEnum) As Boolean
        Return String.Compare(serverValue, matchValue.ToSharePointString(type, True), StringComparison.OrdinalIgnoreCase) = 0
    End Function

    Private Function GenerateQuery(ByVal matchValues As String(), ByVal matchField As SharePointField) As XElement

        Dim query = <Query>
                        <Where>
                        </Where>
                    </Query>

        Dim currentNode = query.Element("Where")

        For i = 0 To matchValues.Count - 1
            Dim eqNode = <Eq>
                             <FieldRef Name=<%= matchField.Name %>/>
                             <Value StorageTZ="True" IncludeTimeValue="True" Type=<%= FieldTypeMappings.GetFieldName(matchField.Type) %>>
                                 <%= matchValues(i).ToSharePointString(matchField.Type, True) %></Value>
                         </Eq>

            If i < (matchValues.Count - 1) Then
                currentNode.Add(<Or>
                                </Or>)
                currentNode = currentNode.Element("Or")
            End If

            currentNode.Add(eqNode)
        Next

        Return query
    End Function

End Class


